﻿import Cesium, { Cartesian3, Color, Event, HeadingPitchRange, HeightReference, JulianDate, Scene, ShadowMap, Viewer } from '../Ces/Cesium'
import TimeSystem from './TimeSystem'
import EarthPinCollection from '../Widgets/EarthPinCollection'
import viewerCesiumNavigationMixin from '../Widgets/Navigation/viewerCesiumNavigationMixin'
import WeatherSystem from '../Weather/WeatherSystem'
import EmptyImageryProvider from '../Provider/EmptyImageryProvider'
import FrameListener from './FrameListener'
import FrameListenerManager from './FrameListenerManager'
import HandlerManager from '../Handler/HandlerManager'
import GeometryManager from '../Geometry/GeometryManager'
import ViewPoint from './ViewPoint'
import LayerManager from '../Layer/LayerManager'
import PrimitiveManager from '../Geometry/PrimitiveManager'
import ScaleListener from './ScaleListener'
import Inject from '../Ces/Inject'
import WeFontAltasManager from '../Display/WeFontAltasManager'
import FluidManager from '../Fluid/FluidManager'
import Util from './Util'
import WeBaseLayerPickerWrapper from '../Widgets/WeBaseLayerPickerWrapper'
import WeComputeEngine from '../Ces/WeComputeEngine'


class We {
  private _guid: string = Cesium.createGuid()
  private _viewer: Viewer
  private _earthPinCollection: EarthPinCollection | undefined
  private _weatherSystem: WeatherSystem | undefined
  private _scaleListener: ScaleListener | undefined
  private _layerManager: LayerManager
  private _primitiveManager: PrimitiveManager | undefined
  private _timeSystem: TimeSystem
  private _preFrameListenerManager: FrameListenerManager | undefined
  private _handlerManager: HandlerManager | undefined
  private _geometryManager: GeometryManager | undefined
  private _preFrameRemoveSubscription: Event.RemoveCallback | undefined
  private _postFrameRemoveSubscription: Event.RemoveCallback | undefined
  private _fontAltasManager: WeFontAltasManager | undefined
  private _postFrameListenerManager: FrameListenerManager | undefined
  private _viewPointScratch: ViewPoint | undefined
  private _fluidManager: FluidManager | undefined
  private _readyPromise = Cesium.defer()
  private _initialized: boolean = false
  private _nextFrameFuncs: any[] = []
  private _baseLayerPickerWrapper: WeBaseLayerPickerWrapper

  constructor(container: HTMLElement, options: any = {}) {
    Inject.init(options.customEntityView ?? true)

    Cesium.Camera.DEFAULT_VIEW_RECTANGLE =
      options.DEFAULT_VIEW_RECTANGLE ??
      Cesium.Rectangle.fromDegrees(111, 10.0, 114, 60.0)

    if (options.CN_Zone ?? true) {
      We.setZh_CN(options.timelineIgnoreDate ?? false)
    }

    if (
      !Cesium.defined(options.imageryProvider) &&
      !Cesium.defined(options.selectedImageryProviderViewModel)
    ) {
      options.imageryProvider = new EmptyImageryProvider()
    }

    this._viewer = new Cesium.Viewer(container, options)

    //@ts-ignore
    this._viewer.scene._computeEngine = new WeComputeEngine(this._viewer.scene._context);

    this._initializeViewer(options)
    this._initializeManagers(options)

    const version = 1.130
    if (parseFloat(Cesium.VERSION) < version) {
      this._viewer.cesiumWidget.showErrorPanel('警告', `请更新cesium到最新版本,兼容的最低版本号为${version}`);
    }
  }

  private _initializeViewer(options: any): void {
    this._viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(
      Cesium.ScreenSpaceEventType.LEFT_CLICK,
    )
    this._viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(
      Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK,
    )
    //@ts-ignore
    this._viewer.cesiumWidget._creditContainer.style.display = 'none'

    if (options.useRightClick ?? true) {
      this._viewer.scene.screenSpaceCameraController.zoomEventTypes = [
        Cesium.CameraEventType.WHEEL,
        Cesium.CameraEventType.PINCH,
      ]
      //@ts-ignore
      this._viewer.scene.screenSpaceCameraController.tiltEventTypes.push(
        Cesium.CameraEventType.RIGHT_DRAG,
      )
    }

    this._viewer.scene.primitives.add(this)
  }

  private _initializeManagers(options: any): void {
    this._layerManager = new LayerManager(this._viewer)
    this._layerManager.shouldAnimate = options.shouldAnimate ?? false
    this._timeSystem = new TimeSystem(this)

    if (options.directionLight ?? false) {
      this.initDirectionalLight()
    }
  }

  initDirectionalLight(): void {
    this._viewer.scene.light = new Cesium.DirectionalLight({
      direction: this._viewer.scene.camera.directionWC,
    })
    this._viewer.scene.globe.dynamicAtmosphereLighting = false

    const directLightListener = new FrameListener()
    directLightListener.update = (scene: Scene) => {
      //@ts-ignore
      scene.light.direction = Cesium.Cartesian3.clone(
        scene.camera.directionWC,
        //@ts-ignore
        scene.light.direction,
      )
    }
    this.preFrameListenerManager.add(directLightListener)
  }

  getReflectShadowMap(options: any): ShadowMap {
    Inject.injectShadowMap()
    const scene = this._viewer.scene
    if (scene.shadowMap) {
      //@ts-ignore
      scene.shadowMap.destroy()
    }
    return (scene.shadowMap = new Cesium.ShadowMap({ scene, ...options }))
  }

  resetTerrain(): void {
    if (!(this._viewer.terrainProvider instanceof Cesium.EllipsoidTerrainProvider)) {
      this._viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider(
        this._viewer.scene.globe.ellipsoid,
      )
    }
  }

  fantasticJumpToViewpoint(
    viewPoint: ViewPoint,
    duration: number,
    animation: { animationDelay?: number; animationHeight?: number; animationDuration?: number },
    callback: (() => void) | null = null,
  ): void {
    Util.We_fantasticJumpToViewpoint(this, viewPoint, duration, animation, callback)
  }

  lookAround(
    target: Cartesian3,
    headingPitchRange: HeadingPitchRange,
    duration: number = 30,
    count: number = 0,
    callback: (() => void) | null = null,
  ): void {
    Util.We_lookAround(this, target, headingPitchRange, duration, count, callback)
  }

  jumpToViewpoint(options: any, duration: number = 0, complete: (() => void) | null = null): void {
    const vp = options instanceof ViewPoint ? options : new ViewPoint(options)
    this._viewer.camera.flyTo({
      destination: vp.cartesion3,
      orientation: {
        heading: vp.heading,
        pitch: vp.pitch,
        roll: vp.roll,
      },
      complete,
      duration: duration ?? 0.0,
    })
  }

  travelWorld(options: any): void {
    Util.We_travelWorld(this, options)
  }

  static setZh_CN(showSimple: boolean): void {
    const padStart = (n: number): string => (n > 0 ? n.toString().padStart(2, '0') : n.toString())
    const julianScratch = new Cesium.JulianDate()

    const formatDateTime = (julianDate: JulianDate, showSimple: boolean): string => {
      julianDate = Cesium.JulianDate.addHours(julianDate, 8, julianScratch)
      const gregorian = Cesium.JulianDate.toGregorianDate(julianDate)
      const minute = gregorian.minute > 0 ? `${padStart(gregorian.minute)}分` : ''

      if (showSimple) {
        if (gregorian.hour === 0)
          return `${padStart(gregorian.month)}月${padStart(gregorian.day)}日`
        if (gregorian.hour === 12)
          return `${padStart(gregorian.day)}日${padStart(gregorian.hour)}时`
        return `${padStart(gregorian.hour)}时${minute}`
      }
      return `${gregorian.year}年${padStart(gregorian.month)}月${padStart(gregorian.day)}日 ${padStart(gregorian.hour)}时${minute}分${padStart(gregorian.second)}秒`
    }

    const formatDate = (julianDate: JulianDate): string => {
      julianDate = Cesium.JulianDate.addHours(julianDate, 8, julianScratch)
      const gregorian = Cesium.JulianDate.toGregorianDate(julianDate)
      return `${gregorian.year}年${gregorian.month}月${gregorian.day}日`
    }

    const formatTime = (julianDate: JulianDate): string => {
      julianDate = Cesium.JulianDate.addHours(julianDate, 8, julianScratch)
      const gregorian = Cesium.JulianDate.toGregorianDate(julianDate)
      return `${padStart(gregorian.hour)}时${padStart(gregorian.minute)}分`
    }

    Cesium.Timeline.prototype.makeLabel = (time: JulianDate): string =>
      formatDateTime(time, showSimple)
    Cesium.AnimationViewModel.defaultDateFormatter = (time: JulianDate): string =>
      formatDate(time)
    Cesium.AnimationViewModel.defaultTimeFormatter = (time: JulianDate): string =>
      formatTime(time)
  }

  get baseLayerPickerWrapper() {
    if (!this._baseLayerPickerWrapper) {
      this._baseLayerPickerWrapper = new WeBaseLayerPickerWrapper(this)
    }
    return this._baseLayerPickerWrapper
  }

  get viewer(): Viewer {
    return this._viewer
  }

  get scene(): Scene {
    return this._viewer.scene
  }

  get layerManager(): LayerManager {
    return this._layerManager
  }

  get guid(): string {
    return this._guid
  }

  get primitiveManager(): PrimitiveManager {
    if (!this._primitiveManager) {
      this._primitiveManager = new PrimitiveManager()
    }
    return this._primitiveManager
  }

  get fontAltasManager(): WeFontAltasManager {
    if (!this._fontAltasManager) {
      this._fontAltasManager = new WeFontAltasManager(this)
    }
    return this._fontAltasManager
  }

  get earthPinCollection(): EarthPinCollection {
    if (!this._earthPinCollection) {
      this._earthPinCollection = new EarthPinCollection(this)
    }
    return this._earthPinCollection
  }

  get geometryManager(): GeometryManager {
    if (!this._geometryManager) {
      this._geometryManager = new GeometryManager()
    }
    return this._geometryManager
  }

  get scaleListener(): ScaleListener {
    if (!this._scaleListener) {
      this._scaleListener = new ScaleListener()
      this.postFrameListenerManager.add(this._scaleListener)
    }
    return this._scaleListener
  }

  get preFrameListenerManager(): FrameListenerManager {
    if (!this._preFrameListenerManager) {
      this._preFrameListenerManager = new FrameListenerManager()
      this._preFrameRemoveSubscription = this._viewer.scene.preRender.addEventListener(
        (scene, time) => {
          this._preFrameListenerManager!.update(scene, time)
        },
      )
    }
    return this._preFrameListenerManager
  }

  get postFrameListenerManager(): FrameListenerManager {
    if (!this._postFrameListenerManager) {
      this._postFrameListenerManager = new FrameListenerManager()
      this._postFrameRemoveSubscription = this._viewer.scene.postRender.addEventListener(
        (scene, time) => {
          this._postFrameListenerManager!.update(scene, time)
        },
      )
    }
    return this._postFrameListenerManager
  }

  get debugShowFramesPerSecond(): boolean {
    return this._viewer.scene.debugShowFramesPerSecond
  }

  set debugShowFramesPerSecond(value: boolean) {
    this._viewer.scene.debugShowFramesPerSecond = value
  }

  get showTimeLine(): boolean {
    //@ts-ignore
    return this._viewer._timeline?.container.style.display !== 'none'
  }

  set showTimeLine(value: boolean) {
    //@ts-ignore
    if (this._viewer._timeline) {
      //@ts-ignore
      this._viewer._timeline.container.style.display = value ? 'block' : 'none'
    }
  }

  get showAnimation(): boolean {
    //@ts-ignore
    return this._viewer._animation?.container.style.display !== 'none'
  }

  set showAnimation(value: boolean) {
    //@ts-ignore
    if (this._viewer._animation) {
      //@ts-ignore
      this._viewer._animation.container.style.display = value ? 'block' : 'none'
    }
  }

  get backgroundColor(): Color {
    return this._viewer.scene.backgroundColor
  }

  set backgroundColor(value: Color) {
    this._viewer.scene.backgroundColor = value
  }

  get showSkyBox(): boolean {
    return this._viewer.scene.skyBox.show
  }

  set showSkyBox(value: boolean) {
    this._viewer.scene.skyBox.show = value
  }

  get enableControl(): boolean {
    return this._viewer.scene.screenSpaceCameraController.enableRotate
  }

  set enableControl(value: boolean) {
    const controller = this._viewer.scene.screenSpaceCameraController
    controller.enableRotate = value
    controller.enableTranslate = value
    controller.enableZoom = value
    controller.enableTilt = value
  }

  get display(): boolean {
    //@ts-ignore
    return this._viewer.cesiumWidget.container.style.display !== 'none'
  }

  set display(value: boolean) {
    this._viewer.cesiumWidget.useDefaultRenderLoop = value
    //@ts-ignore
    this._viewer.cesiumWidget.container.style.display = value ? 'block' : 'none'
  }

  get handlerManager(): HandlerManager {
    if (!this._handlerManager) {
      this._handlerManager = new HandlerManager(this)
    }
    return this._handlerManager
  }

  get timeSystem(): TimeSystem {
    return this._timeSystem
  }

  get showNavigationWidget(): boolean {
    //@ts-ignore
    return this._viewer.cesiumNavigation?.container.style.display !== 'none'
  }

  set showNavigationWidget(value: boolean) {
    //@ts-ignore
    if (!this._viewer.cesiumNavigation) {
      const options = { enableCompass: true, enableZoomControls: true, enableDistanceLegend: true }
      //@ts-ignore
      this._viewer.extend(viewerCesiumNavigationMixin, options)
    }
    //@ts-ignore
    this._viewer.cesiumNavigation!.container.style.display = value ? 'block' : 'none'
  }

  get showDistanceLengthWidget(): boolean {
    //@ts-ignore
    return this._viewer.cesiumNavigation?.distanceLegendDiv.style.display !== 'none'
  }

  set showDistanceLengthWidget(value: boolean) {
    //@ts-ignore
    if (this._viewer.cesiumNavigation) {
      //@ts-ignore
      this._viewer.cesiumNavigation.distanceLegendDiv.style.display = value ? 'block' : 'none'
    }
  }

  get showToolbar(): boolean {
    //@ts-ignore
    return this._viewer._toolbar?.style.display !== 'none'
  }

  set showToolbar(value: boolean) {
    //@ts-ignore
    if (this._viewer._toolbar) {
      //@ts-ignore
      this._viewer._toolbar.style.display = value ? 'block' : 'none'
    }
  }

  get showZoomWidget(): boolean {
    //@ts-ignore
    return this._viewer.cesiumNavigation?.distanceLegendDiv.style.display !== 'none'
  }

  set showZoomWidget(value: boolean) {
    //@ts-ignore
    if (this._viewer.cesiumNavigation) {
      //@ts-ignore
      this._viewer.cesiumNavigation.distanceLegendDiv.style.display = value ? 'block' : 'none'
    }
  }

  get viewPoint(): ViewPoint {
    if (!this._viewPointScratch) {
      this._viewPointScratch = new ViewPoint()
    }
    return ViewPoint.fromCamera(this._viewer.camera, this._viewPointScratch)
  }

  get weatherSystem(): WeatherSystem {
    if (!this._weatherSystem) {
      this._weatherSystem = new WeatherSystem(this)
    }
    return this._weatherSystem
  }

  get hasTerrain(): boolean {
    return !(this._viewer.terrainProvider instanceof Cesium.EllipsoidTerrainProvider)
  }

  get heightReference(): HeightReference {
    return this.hasTerrain ? Cesium.HeightReference.CLAMP_TO_GROUND : Cesium.HeightReference.NONE
  }

  get fluidManager(): FluidManager {
    if (!this._fluidManager) {
      this._fluidManager = new FluidManager()
    }
    return this._fluidManager
  }

  get readyPromise(): Promise<boolean> {
    return this._readyPromise.promise
  }

  nextFrame(func: (frameState: any) => void) {
    this._nextFrameFuncs.push(func)
  }

  update(frameState: any): void {

    frameState.multiplier = this.viewer.clock.multiplier

    frameState.scaleListener = this.scaleListener
    this._handlerManager?.update(frameState)
    this._timeSystem?.update(frameState)
    this._layerManager?.update(frameState)
    this._primitiveManager?.update(frameState)
    this._geometryManager?.update(frameState)
    this._weatherSystem?.update(frameState)
    this._fluidManager?.update(frameState)

    if (!this._initialized) {
      this._initialized = true
      this._readyPromise.resolve(true)
    }

    this._nextFrameFuncs.forEach(f => {
      f(frameState)
    })
    this._nextFrameFuncs.length = 0
  }

  destroy(): void {
    this.destroy = () => { } // Prevent double destruction

    const destroyObject = (object, message) => {
      message = message ?? "This object was destroyed, i.e., destroy() was called.";

      const throwOnDestroyed = () => {
        debugger
        //>>includeStart('debug', pragmas.debug);
        throw new Cesium.DeveloperError(message);
        //>>includeEnd('debug');
      }

      for (const key in object) {
        if (typeof object[key] === "function") {
          object[key] = throwOnDestroyed;
        }
      }

      object.isDestroyed = () => {
        return true;
      }

      return undefined;
    }
    this._preFrameRemoveSubscription?.()
    this._postFrameRemoveSubscription?.()

    this._fluidManager?.destroy()
    this._layerManager?.destroy()
    this._earthPinCollection?.destroy()
    this._handlerManager?.destroy()
    this._timeSystem?.destroy()
    this._primitiveManager?.destroy()
    this._geometryManager?.destroy()
    this._weatherSystem?.destroy()
    this._fontAltasManager?.destroy()

    try {
      this._viewer?.destroy()
    } catch (e) {
      console.log(e)
    }
    return destroyObject(this, 'WE is destroyed.')
  }

  isDestroyed() {
    return false
  }
}

export default We
